<?

class InputStack
{
    protected $database;

    function __construct(LoggerDatabase $database) 
    {
            $this->database = $database;
    }
    
    protected function GetVisitorName($ip)
    {
        return $ip;
    }
    
    protected function GetNewVisitor($time, $name)
    {
        $query = 'INSERT INTO `[PREF]visitors` (`start`, `last`, `name`) VALUES('.$time.', '.$time.', '.$name.')';
        $result = $this->database->Query($query);
        if ($result)
            return $this->database->InsertId();
        else
            trigger_error('Database Error: ' . $this->database->Error(), E_USER_ERROR);
    }

    protected function GetNewSession($time, $visitor, $hash)
    {
        $query = 'INSERT INTO `[PREF]sessions` (`visitor`, `hash`, `start`, `last`) ';
        $query .= 'VALUES('.$visitor.', '.$hash.', '.$time.', '.$time.')';
        $result = $this->database->Query($query);
        if ($result)
            return $this->database->InsertId();
        else
            trigger_error('Database Error: '.mysql_error(), E_USER_ERROR);
    }

    protected function GetDataFromIP($time, $hash, $name)
    {

        $query = 'SELECT `id`, `visitor` FROM `[PREF]sessions` ';
        $query .= 'WHERE `hash`='.$hash.' AND `last`>='.$time.' - INTERVAL '.intval(LoggerConfig::$timeSessionExpires).' SECOND';
        $result = $this->database->Query($query);
        if ($result && ($row = $this->database->FetchAssoc($result))) {
            $session = $row['id'];
            $visitor = $row['visitor'];

            $query = 'UPDATE `[PREF]visitors` SET `last`='.$time.', `hits`=`hits`+1, `name`='.$name.' WHERE `id`='.$visitor;
            $result = $this->database->Query($query);

            $query = 'UPDATE `[PREF]sessions` SET `last`='.$time.', `hits`=`hits`+1, `source`=\'ip\' WHERE `id`='.$session;
            $result = $this->database->Query($query);

            return array($visitor, $session);
        }
        return false;
    }

    protected function GetDataFromCookie($time, $hash, $name)
    {
        $visitor = intval($_COOKIE[LoggerConfig::$cookieName]);
        $session = false;
        if (!$visitor)
            return false;

        $query = 'UPDATE `[PREF]visitors` SET `last`='.$time.', `hits`=`hits`+1, `name`='.$name.' WHERE `id`='.$visitor;
        $result = $this->database->Query($query);
        if (!$result || $this->database->AffectedRows() != 1)
            return false;

        $query = 'SELECT `id` FROM `[PREF]sessions` ';
        $query .= 'WHERE `visitor`='.$visitor.' AND `last`>='.$time.' - INTERVAL '.intval(LoggerConfig::$timeSessionExpires).' SECOND';
        $result = $this->database->Query($query);
        if ($result && ($row = $this->database->FetchAssoc($result)))
        {
            $session = $row['id'];
            $query = 'UPDATE `[PREF]sessions` SET `last`='.$time.', `hits`=`hits`+1, `source`=\'cookie\' WHERE `id`='.$session;
            $result = $this->database->Query($query);
        }
        return array($visitor, $session);
    }

    protected function GetIds($time, $hash, $name)
    {
        $visitor        = false;
        $session        = false;
        $new_visitor    = false;
        $new_session    = false;

        $this->database->Query('LOCK TABLES `[PREF]sessions` WRITE, `[PREF]visitors` WRITE');

        if (!isset($_COOKIE[LoggerConfig::$cookieName]))
        {
            $fromIP = $this->GetDataFromIP($time, $hash, $name);
            if ($fromIP !== false)
                list($visitor, $session) = $fromIP;
        }
        else {
            $fromCookie = $this->GetDataFromCookie($time, $hash, $name);
            if ($fromCookie !== false)
                list($visitor, $session) = $fromCookie;
        }
        if (!$visitor)
        {
            $visitor = $this->GetNewVisitor($time, $name);
            $new_visitor = true;
        }
        if ($new_visitor || !$session)
        {
            $session = $this->GetNewSession($time, $visitor, $hash);
            $new_session = true;
        }

        $this->database->Query('UNLOCK TABLES');

        @setcookie(LoggerConfig::$cookieName, $visitor, time() + LoggerConfig::$timeVisitorExpires, '/');
        return array($visitor, $new_visitor, $session, $new_session);
    }

    function PushHit($url, $mimeType, $title, $referer, $agent, $ip, $time = null)
    {
        if (in_array($ip, LoggerConfig::$filteredIps) || in_array($agent, LoggerConfig::$filteredAgents))
            return false;

        if (is_array(LoggerConfig::$filteredUrls) && count(LoggerConfig::$filteredUrls) > 0)
        {
            foreach(LoggerConfig::$filteredUrls as $pattern)
                if (preg_match($pattern, $url))
                    return false;
        }

        $url        = '\''.mysql_escape_string($url).'\'';
        $mimeType   = '\''.mysql_escape_string($mimeType).'\'';
        $title      = '\''.mysql_escape_string($title).'\'';
        $referer    = '\''.mysql_escape_string($referer).'\'';
        $hash       = '\''.md5($ip.$agent).'\'';
        $agent      = '\''.mysql_escape_string($agent).'\'';
        $name       = '\''.mysql_escape_string($this->GetVisitorName($ip)).'\'';
        $ip         = '\''.mysql_escape_string($ip).'\'';
        if (!$time)
            $time = time();
        $time = '\''.date('Y-m-d H:i:s', $time).'\'';

        list($visitor, $new_visitor, $session, $new_session) = $this->GetIds($time, $hash, $name);
        $first_for_visitor = intval($new_visitor);
        $first_for_session = intval($new_session);

        $query = 'INSERT INTO `[PREF]input_stack` (`visitor`, `session`, `time`, `url`, `mimetype`, `title`, `referer`, `agent`, `ip`, `first_for_visitor`, `first_for_session`) ';
        $query .= 'VALUES('.$visitor.', '.$session.', '.$time.', '.$url.', '.$mimeType.', '.$title.', '.$referer.', '.$agent.', '.$ip.', '.$first_for_visitor.', '.$first_for_session.')';
        $this->database->Query($query);
        return true;
    }

    function PopHit($count, $time, &$oldestTime)
    {
        $timeEscaped = '\''.mysql_escape_string($time).'\'';
        $count = intval($count);

        $this->database->Query('LOCK TABLES `[PREF]input_stack` WHERE, `h` WRITE,  `s` WRITE,  `v` WRITE');

//        $query = 'SELECT * FROM `[PREF]input_stack` WHERE `time`<' . $timeEscaped . ' ORDER BY `id` LIMIT 0, ' . $count;
        $query = 'SELECT `h`.`id`, `h`.`visitor`, `h`.`session`, `h`.`time`, `h`.`url`, `h`.`mimetype`, `h`.`title`, `h`.`referer`, ';
        $query .= '`h`.`agent`, `h`.`ip`, `h`.`first_for_visitor`, `h`.`first_for_session`, `s`.`source` AS `session_source`, `v`.`name` AS `visitor_name` ';
        $query .= 'FROM `[PREF]input_stack` AS `h` LEFT JOIN `[PREF]sessions` AS `s` ON `s`.`id`=`h`.`session` LEFT JOIN `[PREF]visitors` AS `v` ON `v`.`id`=`h`.`visitor` ';
        $query .= 'WHERE `h`.`time`<' . $timeEscaped . ' ORDER BY `h`.`id` LIMIT 0, ' . $count;
        $result = $this->database->Query($query);
        $arIds = array();
        $arHits = array();
        if ($result)
        {
            while ($row = $this->database->FetchAssoc($result))
            {
                if ($oldestTime === false || strtotime($row['time']) < strtotime($oldestTime))
                    $oldestTime = $row['time'];

                $arIds[] = $row['id'];
                $arHits[] = array(
                    'source' => $row,
                    'modules' => array()
                );
            }
            $query = 'DELETE FROM `[PREF]input_stack` WHERE `id` IN ('.implode(',',$arIds).')';
            $this->database->Query($query);
        }

        $this->database->Query('UNLOCK TABLES');

        return $arHits;
    }

    public function DetectSpam($time)
    {
        $timeEscaped = '\''.mysql_escape_string($time).'\'';
        $detectedSpam = array();
        $SelectSQL = 'SELECT `id` FROM `[PREF]sessions` WHERE `source`=\'ip\' AND `last`<' . $timeEscaped;
        $result = $this->database->Query($SelectSQL);
        while ($row = $this->database->FetchAssoc($result))
        {
            $session = $row['id'];
            $urls       = array();
            $referers   = array();
            $all        = 0;
            $externals  = 0;
            $empty      = 0;
            $fromHome   = 0;

            $SelectSQL = 'SELECT `url`, `referer` FROM `[PREF]input_stack` WHERE `session`='.intval($session);
            $resultHits = $this->database->Query($SelectSQL);
            while ($rowHit = $this->database->FetchAssoc($resultHits))
            {
                $url = $rowHit['url'];
                $referer = $rowHit['referer'];
                $all++;

                if (!isset($urls[$url]))
                    $urls[$url] = 1;
                else
                    $urls[$url]++;

                if (strlen($referer) == 0)
                {
                    $empty++;
                    continue;
                }

                if (!isset($referers[$referer]))
                    $referers[$referer] = 1;
                else
                    $referers[$referer]++;

                $parseResult = @parse_url($referer);
                if ($parseResult !== false)
                {
                    $refererServer = $parseResult['host'];
                    if (isset($parseResult['port']))
                        $refererServer .= ':' . $parseResult['port'];
                    if (!in_array($refererServer, LoggerConfig::$myServers))
                        $externals++;
                    if (!isset($parseResult['path']) || ($parseResult['path'] == '/' && !isset($parseResult['query'])))
                        $fromHome++;
                }
            }

            if ($all == $externals && $all == $fromHome && (count($urls) == 1 || count($referers) == 1))
            {
                $DeleteSQL = 'DELETE FROM `[PREF]input_stack` WHERE `session`='.intval($session);
                $this->database->Query($DeleteSQL);
                $detectedSpam =  array_merge($detectedSpam, $referers);
            }
        }
        return array_keys($detectedSpam);
    }

    function Purge($oldestTime)
    {
        if (!isset(LoggerConfig::$timeSessionExpires))
            return false;

        if ($oldestTime === false)
            return false;

        $oldestTime = '\''.mysql_escape_string($oldestTime).'\'';

        $DeleteSQL = 'DELETE FROM `[PREF]visitors` WHERE `last`<'.$oldestTime.' AND `last` < NOW() - INTERVAL '.intval(LoggerConfig::$timeVisitorExpires).' SECOND';
        $this->database->Query($DeleteSQL);
        $DeleteSQL = 'DELETE FROM `[PREF]sessions` WHERE `last`<'.$oldestTime.' AND `last` < NOW() - INTERVAL '.intval(LoggerConfig::$timeSessionExpires).' SECOND';
        $this->database->Query($DeleteSQL);

        $OptimizeSQL = 'OPTIMIZE TABLE `[PREF]visitors`';
        $this->database->Query($OptimizeSQL);
        $OptimizeSQL = 'OPTIMIZE TABLE `[PREF]sessions`';
        $this->database->Query($OptimizeSQL);
        $OptimizeSQL = 'OPTIMIZE TABLE `[PREF]input_stack`';
        $this->database->Query($OptimizeSQL);

        return true;
    }
}